Eigenface Gesichtserkennung

  • Autor: Prof. Dr. Johannes Maucher
  • Datum: 27.11.2015

Übersicht Ipython Notebooks im Data Mining Praktikum

Einführung

Lernziele:

In diesem Versuch sollen Kenntnisse in folgenden Themen vermittelt werden:

  • Gesichtserkennung: mit der Eigenface Methode.
  • Principal Component Analysis
  • Bildverarbeitung mit Python.

Sämtliche Verfahren und Algorithmen werden in Python implementiert.

Theorie zur Vorbereitung

Die Gesichtserkennung kann mit unterschiedlichen Ansätzen realisiert werden. In diesem Versuch wird ausschließlich der Eigenface-Ansatz vorgestellt. Dieser Ansatz basiert auf der Principal Component Analysis (PCA) und wurde erstmals in M. Turk, A. Pentland; Eigenfaces for Recognition vorgestellt. Die Eigenface-Methode weist eine gute Performance im Fall biometrisch aufgenommener Gesichtsbilder auf.

Das Prinzip der Eigenface Gesichtserkennung

Bilder mit $C$ Pixeln in der Breite und $R$ Pixeln in der Höhe können als $R \times C$ Matrizen abgespeichert werden. Handelt es sich um ein Schwarz-Weiß- oder Graustufen-Bild, dann wird pro Bild nur eine derartige Matrix benötigt. Der Eintrag in der i.ten Zeile und j.ten Spalte dieser Matrix definiert den Grauwert des entsprechenden Pixels. In Farbbildern werden je nach benutztem Farbraum mehrere Matrizen pro Bild benötigt, wobei jede Matrix einen Farbkanal des Bildes repräsentiert. Für ein RGB-Bild werden z.B. 3 Matrizen für die Farbkanäle Rot, Grün und Blau benötigt. \

Im Folgenden wird von quadratischen Graubildern mit $N \times N$ Pixeln ausgegangen. Wird jedes Pixel als ein Merkmal betrachtet, dann existieren insgesamt $N^2$ Merkmale, das Bild kann auch als ein Punkt im $N^2$-dimensionalen Raum betrachtet werden. Bilder der Auflösung $256 \times 256$ müßten also im $65536$-dimensionalen Raum beschrieben werden. Entsprechend komplex wäre die notwendige Verarbeitung. Ist jedoch bekannt, dass in einer Menge von Bildern jeweils ein gleichartiges Objekt abgebildet ist, z.B. wenn alle Bilder ausschließlich je ein Gesicht enthalten, dann existieren große Abhängigkeiten zwischen diesen Bildern. Geometrisch ausgedrückt bedeutet dies, dass die Punkte, welche die Menge der gleichartigen Bilder beschreiben, nicht gleichmäßig über den $N^2$-dimensionalen Raum verteilt sind, sondern in einen relativ kleinen Unterraum mit $K<

Das Problem besteht nun zunächst darin, aus einer Menge von Bildern der gleichen Kategorie die relevanten Merkmale zu finden. Dieses Problem wird durch die Principal Component Analysis (PCA) gelöst. Die PCA, findet in einer Menge von Bildern der gleichen Kategorie die Hauptachsen, also die Richtungen im $N^2$-dimensionalen Raum, entlang derer die Varianz zwischen den gegebenen Bildern am stärksten ist. Der $N^2$-dimensionale Pixelraum wird dann in einen Raum, der durch die gefundenen Hauptachsen aufgespannt wird, transformiert. In diesem in der Anzahl der Dimensionen stark reduzierten Raum wird dann die Bilderkennung durchgeführt. Der hier skizzierte Ansatz der Eigenfaces für die Gesichtserkennung wurde erstmalig in M. Turk, A. Pentland; Eigenfaces for Recognition beschrieben.

Genereller Ablauf

Die Gesichtserkennung besteht aus 2 Phasen. In der Trainingsphase werden die Gesichtsbilder der zu erkennenden Personen eingelesen und für diese mit der PCA der Eigenface-Raum berechnet. In der Erkennungsphase wird ein neu aufgenommenes Bild in den Eigenface-Raum transformiert und dort dem naheliegendsten Bild aus der Trainingsmenge zugeordnet.

Trainingsphase

  1. Lese Gesichtsbilder der Personen, die erkannt werden sollen ein. Die Menge dieser Bilder definiert das Trainingsset
  2. Berechne mit der PCA den Eigenface-Raum. Dabei werden nur die K Dimensionen, welche zu den Eigenvektoren mit den größten Eigenwerten gehören ausgewählt. Die zu den K Dimensionen (Eigenvektoren) gehörenden Bilder sind die Eigenfaces.
  3. Transformiere jedes Bild der Trainingsmenge in den Eigenface-Raum und erhalte so die entsprechende Repräsentation des Bildes als Punkt im Eigenface-Raum.

Erkennungsphase

  1. Transformiere das zu erkennende Bild in den Eigenface-Raum und berechne dort die Koordinaten des Bildes hinsichtlich aller K-Dimensionen (Eigenfaces)
  2. Bestimme ob das zu erkennende Bild überhaupt eine Gesicht darstellt
  3. Bestimme ob das Gesicht zu einer bekannten Person, deren Bild in der Trainingsmenge enthalten ist, gehört.

Update (optional)

Füge das erkannte Bild zur Menge der Trainingsbilder hinzu und führe die Schritte der Trainingsphase durch.

Bestimmung der Eigenfaces

Es werden zunächst $M$ Gesichtsbilder der zu erkennenden Personen eingelesen (von jeder zu erkennenden Personen möglichst mehrere Bilder). Es wird davon ausgegangen, dass jedes der Bilder $C$ Pixel breit und $R$ Pixel hoch ist. Das Bild kann dann als $R \times C$ Matrix dargestellt werden. Im Fall eines Graustufenbildes repräsentieren die Pixelwerte den entsprechenden Grauwert. Nach dem Einlesen werden die Bildmatrizen als eindimensionale Vektoren dargestellt. Für diese Umformung werden die Zeilen jeder Matrix von oben nach unten ausgelesen und hintereinander gereiht. Jedes Bild wird dann durch einen Vektor der Länge $Z=R \cdot C$ repräsentiert und kann als Punkt im Z-dimensionalen Raum dargestellt werden. Die M Bildvektoren werden im folgenden mit $$\Gamma _1, \Gamma_2, \ldots, \Gamma_M$$ bezeichnet.

Im nächsten Schritt wird das Durchschnittsbild berechnet $$ \overline{\Gamma}=\frac{1}{M}\sum_{i=1}^{M}{\Gamma_{i}} $$

Dieses Durchschnittsbild wird von allen Bildern $\Gamma_i$ abgezogen. Die Menge der so gewonnenen Bildrepräsentationen $$ \Phi_i=\Gamma_i - \overline{\Gamma} $$

ist dann mittelwertsfrei. Die Menge $\Phi_1, \Phi_2, \ldots, \Phi_M$ wird dann einer Principal Component Analysis (PCA) (siehe auch J. Maucher; Feature Selection and Extraction) unterzogen. Hierzu werden die mittelwertfreien Bildrepräsentationen $\Phi_i$ in die Spalten einer Matrix geschrieben. Diese Matrix wird im Folgenden mit $X$ bezeichnet. Unter der Annahme, dass die $\Phi_i$ bereits als Spaltenvektoren vorliegen, ist die Matrix $X$ definiert als:

$$ X=\left[ \Phi_1, \Phi_2, \ldots, \Phi_M \right]. $$

Die entsprechende Kovarianzmatrix ist dann $$ CV=X \cdot X^T. $$

Für die PCA müssten als nächstes eigentlich die Eigenvektoren und Eigenwerte der Kovarianzmatrix $CV$ berechnet werden. Für den vorliegenden Fall kann allerdings die hierfür notwendige Berechnung aus Komplexitätsgründen nicht realisiert werden. Man beachte dass die Matrix $CV$ $Z$ Spalten und $Z$ Zeilen enthält ($Z$ ist die Anzahl der Pixel in einem Bild) und für diese $Z$ Eigenvektoren und Eigenwerte existieren. Wie in M. Turk, A. Pentland; Eigenfaces for Recognition beschrieben, existieren im Fall, dass die Anzahl der Bilder $M$ wesentlich kleiner als die Anzahl der Pixel $Z$ ist, nur $M-1$ relevante Eigenvektoren, die Eigenwerte aller anderen Eigenvektoren liegen nahe bei Null. Der in M. Turk, A. Pentland; Eigenfaces for Recognition beschriebene Ansatz geht nun von der $M \times M$ Matrix

$$ X^T \cdot X $$

aus, für welche die Eigenvektoren und Eigenwerte für eine moderate Bildanzahl $M$ gut berechnet werden können. Per Definition gilt für die Eigenvektoren $\mathbf{v}_i$ und Eigenwerte $\mu_i$ dieser Matrix:

$$ X^T \cdot X \cdot \mathbf{v}_i = \mu_i \mathbf{v}_i . $$

Werden beide Seiten dieser Matrix linksseitig mit der Matrix $X$ multipliziert,

$$ X \cdot X^T \cdot X \cdot \mathbf{v}_i = \mu_i X \mathbf{v}_i, $$

dann ist daraus zu erkennen, dass die $M$ Vektoren $$ \mathbf{u}_i=X \mathbf{v}_i $$

die Eigenvektoren der Matrix $$CV=X \cdot X^T$$ sind. D.h. es können zunächst die M Eigenvektoren der relativ kleinen Matrix $X^T \cdot X$ bestimmt und aus diesen durch eine einfache Multiplikation mit der Matrix $X$ die relevanten Eigenvektoren der Matrix $CV$ berechnet werden. Da die Matrix $X$ die $M$ Bildrepräsentationen $\Phi_i$ als Spalten enthält, können die gesuchten Eigenvektoren auch als Linearkombination der $M$ Bilder der Trainingsmenge beschrieben werden:

$$ \mathbf{u}_i=\sum_{k=1}^{M}{v_{i,k}\Phi_k} $$

wobei mit $v_{i,k}$ die $k.$te Komponente des Vektors $\mathbf{v}_i$ bezeichnet wird. Die Eigenvektoren $\mathbf{u}_i$ werden auch Eigenfaces genannt. Per Definition sind die Eigenvektoren paarweise orthogonal. Jeder Eigenvektor ist ein Spaltenvektor mit $Z$ (=Anzahl der Pixel) Komponenten.

Die $M$ Eigenvektoren werden dann entsprechend der Größe der zugehörigen Eigenwerte $\mu_i$ geordnet. Für die weiteren Schritte kann zum Zwecke einer weiteren Komplexitätsreduktion eine Untermenge der $K$ relevantesten Eigenvektoren benutzt werden (also der $K$ Eigenvektoren mit den höchsten Eigenwerten). Beispielsweise ist in M. Turk, A. Pentland; Eigenfaces for Recognition für die Erkennung von $M=16$ Personen und eine Auflösung von $256 \times 256$ Pixel meist $K=7$ Eigenvektoren für eine gute Erkennung ausgereicht.

Gesichtserkennung im Eigenspace

Die $K$ ausgewählten Eigenvektoren $\mathbf{u}_1,\mathbf{u}_2,\ldots \mathbf{u}_K$ spannen einen $K-$dimensionalen Raum, den sogenannten Eigenspace auf. Die $K$ Vektoren repräsentieren die $K$ Merkmale hinsichtlich derer die Bilder der Trainingsdatenmenge am stärksten variieren.

Für die Bilderkennung wird jetzt jedes Bild, also sowohl die Bilder aus der Trainingsmenge als auch die zu erkennenden Bilder, in den Eigenspace transformiert. Jedes Bild stellt einen Punkt im Eigenspace dar. Für die Erkennung kann einfach die Distanz des zu erkennenden Bildes zu allen Bildern der Trainingsmenge berechnet werden. Das zu erkennende Bild wird der Person (Bildklasse) zugeordnet, deren Punkt im Eigenspace dem Punkt des zu erkennenden Bildes am nächsten liegt.

Die $K$ Komponenten eines Trainingsbildes werden berechnet, indem das Bild auf den jeweiligen Eigenvektor projiziert wird. Demnach ist die $k.$te Komponente des $i.$ten Trainingsbildes $\Phi_i$: $$ \omega_{k,i}=\mathbf{u}_k^T \Phi_i $$

Der dem Bild $\Phi_i$ entsprechende Punkt im Eigenspace ist dann $$ \mathbf{w}_i=[\omega_{1,i},\omega_{2,i},\ldots,\omega_{K,i}]. $$

Wird mit $\Gamma$ das zu erkennende Bild und mit $\Phi=\Gamma - \overline{\Gamma}$ die um den Mittelwert der Trainingsbilder subtrahierte Version des Bildes bezeichnet, dann sind $$ \omega_{k}=\mathbf{u}_k^T \Phi $$

die Koordinaten der Projektion von $\Phi$ in den Eigenspace und der dieses Bild repräsentierende Punkt $$ \mathbf{w}=[\omega_{1},\omega_{2},\ldots,\omega_{K}]. $$

Das zu erkennende Bild wird dann dem Trainingsbild $\Phi_j$ zugeordnet, für welches gilt: $$ j=argmin_{i} \left\{ d(\mathbf{w},\mathbf{w}_i) \right\} $$

wobei mit $d(\mathbf{w},\mathbf{w}_i)$ die euklidische Distanz zwischen den Projektionen von $\Phi$ und $\Phi_i$ bezeichnet wird.

Optional: Falls $\Phi_i$ nicht das einzige Bild einer Person in der Trainingsmenge ist, sondern für die entsprechende Person mehrere Trainingsbilder vorliegen, wird in der Distanzberechnung nicht $\Phi_i$, sondern der Mittelwert über alle zu dieser Person gehörenden Bilder eingesetzt:

$$ \overline{\Phi}=\frac{1}{|W|}\sum_{w \in W}^{}{\Phi_w} . $$

Dabei bezeichnet $W$ die Menge aller der Indizees $w$, für die die $\Phi_w$ zur gleichen Person gehören. Im Praktikumsversuch muss diese Option nicht implementiert werden. Die im folgenden Abschnitt beschriebene Versuchsdurchführung bezieht sich auf den Fall, dass nur die Distanz zu Einzelbildern berechnet wird und das nächstliegende Bild ausgegeben wird.

Für die Mindestdistanz $$ \epsilon =\min_{i} \left\{ d(\Phi,\Phi_i) \right\} $$

wird in der Regel eine Schwelle $T$ definiert. Wenn $\epsilon > T$ ist, also eine relativ große Distanz zwischen dem zu erkennenden Bild und dem nächstliegenden Bild aus der Trainingsmenge besteht, wird davon ausgegangen, dass es sich um ein unbekanntes Gesicht handelt. Optional könnte dieses unbekannte Gesicht in die Trainingsmenge aufgenommen werden.

Schließlich muss noch der Fall behandelt werden, dass das eingelesene Bild kein Gesicht darstellt. Aufgrund der starken Projektion vom ursprünglichen Bildraum in den Eigenspace kann dieser Fall nicht durch eine Schwelle auf den Fehler $\epsilon$ erkannt werden. Es kann durchaus sein, dass ein Nicht-Gesichtsbild in die Umgebung eines Gesichtsbild im Eigenspace projiziert wird. Ein Nicht-Gesichtsbild wird aber eine relativ große Distanz $d(\Phi,\Phi_f)$ zwischen

$$ \Phi=\Gamma - \overline{\Gamma} $$

und der Repräsentation im Eigenspace $$ \Phi_f=\sum_{k=1}^{K}{\omega_k}\mathbf{u}_k $$ aufweisen. Durch die Definition einer weiteren Schwelle $S$ auf $d(\Phi,\Phi_f)$ kann also erkannt werden, ob es sich überhaupt um ein Gesicht handelt. Im Versuch ist davon auszugehen, dass nur Gesichtsbilder verwendet werden, d.h. es muss nur der Test gegen die Schwelle $\epsilon$ implementiert werden.

Vor dem Versuch zu klärende Fragen

  • Was sind Eigenvektoren und Eigenwerte?

Eigenvektoren und Eigenwerte existieren immer in Bezug zu einer Matrix.

Ein Vektor v ist dann ein Eigenvektor der Matrix M wenn gilt:

M * v = v * lambda

Wobei lambda ein Faktor ist der als Eigenwert bezeichnet wird


  • Was versteht man unter Eigenfaces?

Die Eigenvektoren des Durchschnittbildes werden als Eigenfaces bezeichnet. Je geriger der dazu gehörige Eigenwert ist, desto aussagekräftiger ist diese.
Per Definition sind die Eigenfaces paarweise orthogonal.


* Wie werden mit dem Python Modul \emph{Image} Bilder in ein Python-Programm geladen?

Versuchsdurchführung

Einlesen der Gesichtsbilder in Numpy Arrays

Laden Sie vom Skripteserver das Verzeichnis Gesichtsbilder auf Ihren Rechner. Darin enthalten sind

  • das Unterverzeichnis training, welches je 3 Bilder jedes Studenten enthält,
  • das Unterverzeichnis test, welches die nicht in training enthaltenen Bilder enthält. Ein Bild von jedem Studenten.

Mit der unten gegebenen Funktion parseDirectory(directoryName,extension) wird eine Liste aller Dateinamen des Typs extension im Verzeichnis directoryName angelegt.

Aufgabe:

1.Legen Sie mit dieser Funktion eine Liste mit allen Dateinamen des Typs extension='.png' im Verzeichnis training (enthält die Trainingsbilder) an.

In [461]:
%matplotlib inline
from os.path import isdir,join,normpath
from os import listdir
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import image as mplimg
In [462]:
def parseDirectory(directoryName,extension):
    '''This method returns a list of all filenames in the Directory directoryName. 
    For each file the complete absolute path is given in a normalized manner (with 
    double backslashes). Moreover only files with the specified extension are returned in 
    the list.
    '''
    if not isdir(directoryName): return
    imagefilenameslist=sorted([
        normpath(join(directoryName, fname))
        for fname in listdir(directoryName)
        if fname.lower().endswith('.'+extension)            
        ])
    return imagefilenameslist
In [463]:
image_paths = parseDirectory('training', 'png')

print image_paths
['training/1-2.png', 'training/1-3.png', 'training/1-4.png', 'training/10-2.png', 'training/10-3.png', 'training/10-4.png', 'training/11-2.png', 'training/11-3.png', 'training/11-4.png', 'training/12-2.png', 'training/12-3.png', 'training/12-4.png', 'training/13-2.png', 'training/13-3.png', 'training/13-4.png', 'training/14-2.png', 'training/14-3.png', 'training/14-4.png', 'training/15-2.png', 'training/15-3.png', 'training/15-4.png', 'training/16-2.png', 'training/16-3.png', 'training/16-4.png', 'training/17-2.png', 'training/17-3.png', 'training/17-4.png', 'training/18-2.png', 'training/18-3.png', 'training/18-4.png', 'training/19-1.png', 'training/19-3.png', 'training/19-4.png', 'training/1b-2.png', 'training/1b-3.png', 'training/1b-4.png', 'training/2-2.png', 'training/2-3.png', 'training/2-4.png', 'training/20-2.png', 'training/20-3.png', 'training/20-4.png', 'training/3-2.png', 'training/3-3.png', 'training/3-4.png', 'training/4-2.png', 'training/4-3.png', 'training/4-4.png', 'training/5-2.png', 'training/5-3.png', 'training/5-4.png', 'training/6-2.png', 'training/6-3.png', 'training/6-4.png', 'training/7-2.png', 'training/7-3.png', 'training/7-4.png', 'training/8-2.png', 'training/8-3.png', 'training/8-4.png', 'training/9-2.png', 'training/9-3.png', 'training/9-4.png']
In [464]:
test_img_paths = parseDirectory('test', 'png')

print test_img_paths
['test/1-1.png', 'test/10-1.png', 'test/11-1.png', 'test/12-1.png', 'test/13-1.png', 'test/14-1.png', 'test/15-1.png', 'test/16-1.png', 'test/17-1.png', 'test/18-1.png', 'test/19-2.png', 'test/1b-1.png', 'test/2-1.png', 'test/20-1.png', 'test/3-1.png', 'test/4-1.png', 'test/5-1.png', 'test/6-1.png', 'test/7-1.png', 'test/8-1.png', 'test/9-1.png']

2.Implementieren Sie eine Funktion readImageToNumpyData(imageList), der eine Liste aller Dateinamen der Trainingsbilder übergeben wird. Die Funktion gibt ein Numpy-Array zurück. Jede Zeile dieses Arrays enthält ein .png-Bild in serialisierter Form. Hierzu ist jedes einzelne Bild zunächst mit der Funktion matplotlib.image.imread(filename) in ein zweidimensionales Numpy Array img zu lesen. Durch den Aufruf von img.shape=(1,-1) wird das zweidimensionale Numpy Array zu einem eindimensionalen Array serialisiert. Danach muss eine Normierung aller Werte in den Bereich zwischen 0 und 1 durchgeführt werden. Hierzu müssen alle Pixelwerte eines Bildes durch den im jeweiligen Bild vorkommenden Maximalwert geteilt werden.

In [465]:
import numpy as np
import matplotlib

def readImageToNumpyData(imageList):
    arr = []
    
    for image in imageList:
        image_1dim = matplotlib.image.imread(image)
        image_1dim.shape = (1, -1)
        
        np_image_1dim = np.array(image_1dim)        
        row = np_image_1dim / np_image_1dim.max()
        
        arr.append(row)
        
    return np.array(arr)
In [466]:
np_images = readImageToNumpyData(image_paths)

np_images
Out[466]:
array([[[ 0.92660552,  0.92660552,  0.92201835, ...,  0.27064219,
          0.27522936,  0.31192663]],

       [[ 0.9245283 ,  0.9245283 ,  0.9245283 , ...,  0.35849059,
          0.35377359,  0.33490568]],

       [[ 0.9395349 ,  0.9395349 ,  0.9395349 , ...,  0.40930232,
          0.40000001,  0.33953491]],

       ..., 
       [[ 0.95217389,  0.95217389,  0.95217389, ...,  0.56521744,
          0.56086957,  0.55217391]],

       [[ 0.94520551,  0.94520551,  0.9497717 , ...,  0.5022831 ,
          0.48858449,  0.48858449]],

       [[ 0.95535713,  0.95535713,  0.9464286 , ...,  0.53125   ,
          0.53125   ,  0.53125   ]]], dtype=float32)
In [467]:
test_np_img = readImageToNumpyData(test_img_paths)

test_np_img
Out[467]:
array([[[ 0.9336493 ,  0.9336493 ,  0.9336493 , ...,  0.39810428,
          0.39336494,  0.39336494]],

       [[ 0.95215309,  0.95215309,  0.95215309, ...,  0.07177033,
          0.06698564,  0.06220096]],

       [[ 0.92753625,  0.92753625,  0.92753625, ...,  0.9468599 ,
          0.9758454 ,  0.9951691 ]],

       ..., 
       [[ 0.92857146,  0.92857146,  0.92857146, ...,  0.54591835,
          0.53571427,  0.53061223]],

       [[ 0.95734596,  0.95734596,  0.95734596, ...,  0.09478673,
          0.10426541,  0.10426541]],

       [[ 0.95175439,  0.95175439,  0.94736844, ...,  0.5131579 ,
          0.51754385,  0.5131579 ]]], dtype=float32)

Berechnung des Durchschnittbildes

Aufgaben:

1.Die von der Funktion convertImgListToNumpyData(imgList) zurückgelieferte Matrix enthält in ihren Zeilen alle Trainingsbilder. Aus diesen Trainingsbildern ist nach der Gleichung für $\overline{\Gamma}$ das Durchschnittsbild zu berechnen, z.B. durch Anwendung der Numpy-Funktion average. Das Durchschnittsbild ist von allen Bildern abzuziehen (Gleichung $\Phi_i$). Das daraus resultierende Numpy-Array enthält die mittelwertfreien Repräsentationen der Trainingsbilder und wird im Folgenden mit NormedArrayOfFaces bezeichnet.

In [468]:
def get_average_pic(img_matrix):    
    average_pic = np.average(img_matrix, axis=0)
    average_pic.shape = (220,150)
    
    return average_pic
In [469]:
average_pic = get_average_pic(np_images)

print average_pic
[[ 0.92694771  0.92710316  0.92661816 ...,  0.96096939  0.96242601
   0.96221101]
 [ 0.92652124  0.92727405  0.92689389 ...,  0.96139318  0.96145004
   0.96175557]
 [ 0.92653888  0.92671037  0.92679435 ...,  0.96176469  0.96109909
   0.96106058]
 ..., 
 [ 0.28871983  0.28848898  0.28502822 ...,  0.31370559  0.31944615
   0.32455501]
 [ 0.28301984  0.27918607  0.27794144 ...,  0.30205891  0.30742636
   0.31527409]
 [ 0.27997166  0.27346161  0.27268091 ...,  0.30109414  0.30386668
   0.30944985]]
In [470]:
def remove_average_pic(image_array, average_img):
    subtract_vec = np.copy(average_img)
    subtract_vec.shape = (1, -1)
    
    return image_array - subtract_vec
In [471]:
normed_array_of_faces = remove_average_pic(np_images, average_pic)
normed_array_of_faces = [x[0] for x in normed_array_of_faces]

print normed_array_of_faces
[array([-0.00034219, -0.00049764, -0.00459981, ..., -0.03045195,
       -0.02863732,  0.00247678], dtype=float32), array([-0.00241941, -0.00257486, -0.00208986, ...,  0.05739644,
        0.04990691,  0.02545583], dtype=float32), array([ 0.01258719,  0.01243174,  0.01291674, ...,  0.10820818,
        0.09613332,  0.03008506], dtype=float32), array([ 0.02354735,  0.01844138,  0.02387691, ..., -0.2367377 ,
       -0.23951024, -0.2500439 ], dtype=float32), array([ 0.01886511,  0.01870966,  0.01919466, ..., -0.24198085,
       -0.23490116, -0.23555823], dtype=float32), array([ 0.02330106,  0.0281207 ,  0.0286057 , ..., -0.25631803,
       -0.25411543, -0.2596986 ], dtype=float32), array([ 0.00823748,  0.00808203,  0.00856704, ...,  0.68501699,
        0.6915037 ,  0.68592054], dtype=float32), array([ 0.01083004,  0.00623018,  0.00227076, ...,  0.67223918,
        0.65613329,  0.65055013], dtype=float32), array([ 0.01110542,  0.01094997,  0.01143497, ...,  0.67678201,
        0.66515988,  0.6551519 ], dtype=float32), array([-0.03554499, -0.02665067, -0.02616566, ..., -0.27846971,
       -0.27671736, -0.27777565], dtype=float32), array([-0.11271846, -0.10892135, -0.10843635, ..., -0.22994789,
       -0.27224612, -0.27387673], dtype=float32), array([-0.02136832, -0.02581561, -0.0253306 , ..., -0.27105123,
       -0.26094824, -0.27511507], dtype=float32), array([ 0.05711603,  0.05696058,  0.05744559, ...,  0.09731224,
        0.02681062,  0.02122745], dtype=float32), array([ 0.05336726,  0.05321181,  0.05369681, ..., -0.0412516 ,
       -0.0637092 , -0.06535536], dtype=float32), array([ 0.05724204,  0.05708659,  0.05757159, ..., -0.04812971,
       -0.06671253, -0.01300716], dtype=float32), array([ 0.00805229,  0.00789684,  0.00338185, ..., -0.18609414,
       -0.18386668, -0.17444985], dtype=float32), array([-0.00947195, -0.0096274 , -0.0091424 , ..., -0.16031745,
       -0.16308999, -0.18323626], dtype=float32), array([ 0.00305229,  0.00289685,  0.00338185, ..., -0.16609414,
       -0.19386669, -0.17944986], dtype=float32), array([ 0.02372491,  0.01908517,  0.01957017, ...,  0.49711213,
        0.52124542,  0.50220937], dtype=float32), array([ 0.03251177,  0.02785182,  0.02833682, ...,  0.00521219,
        0.03397116,  0.1455051 ], dtype=float32), array([ 0.03232831,  0.03217286,  0.03265786, ..., -0.0748498 ,
       -0.01879883, -0.00175753], dtype=float32), array([ 0.02497536,  0.0200122 ,  0.0204972 , ..., -0.02705568,
       -0.01540515, -0.01618063], dtype=float32), array([ 0.01536   ,  0.01520455,  0.01568955, ...,  0.01621357,
        0.01824871,  0.01747325], dtype=float32), array([ 0.01995492,  0.01979947,  0.02028447, ...,  0.00864038,
       -0.00298172, -0.01298967], dtype=float32), array([-0.00615561, -0.00631106, -0.00087559, ..., -0.2317872 ,
       -0.22960925, -0.24014291], dtype=float32), array([-0.00654972, -0.00670516, -0.00622016, ..., -0.23641753,
       -0.22923982, -0.24477324], dtype=float32), array([-0.00241941, -0.00257486, -0.00208986, ..., -0.23977339,
       -0.24254593, -0.24341211], dtype=float32), array([ 0.01085132,  0.01069587,  0.00639617, ..., -0.12406065,
       -0.09812506, -0.10849291], dtype=float32), array([ 0.00963765,  0.0094822 ,  0.00996721, ..., -0.13524048,
       -0.13313498, -0.13384008], dtype=float32), array([ 0.01286709,  0.00808203,  0.00856704, ..., -0.10202007,
       -0.08627409, -0.11037578], dtype=float32), array([-0.06845713, -0.05917865, -0.06341058, ...,  0.41117004,
        0.38952959,  0.41696525], dtype=float32), array([-0.02069771, -0.01564485, -0.01515985, ...,  0.20932254,
        0.27946663,  0.39888352], dtype=float32), array([ 0.02835953,  0.02261752,  0.00634277, ...,  0.35253713,
        0.30507183,  0.27155572], dtype=float32), array([-0.01237482, -0.00750518, -0.00702018, ...,  0.02051392,
        0.00769109, -0.01799256], dtype=float32), array([-0.00839567, -0.00402623,  0.00098366, ...,  0.04732215,
        0.02192518, -0.01080731], dtype=float32), array([ -7.90005922e-03,  -8.05550814e-03,  -2.80863047e-03, ...,
         2.74772942e-02,   2.47047544e-02,   7.39693642e-05], dtype=float32), array([-0.03747404, -0.02710313, -0.02661812, ..., -0.0695152 ,
       -0.05649826,  0.0484449 ], dtype=float32), array([-0.01649994, -0.01665539, -0.01617038, ..., -0.1319399 ,
       -0.05511047,  0.0686596 ], dtype=float32), array([-0.00507271, -0.01043648, -0.00995147, ...,  0.11557254,
        0.21175832,  0.13846681], dtype=float32), array([ 0.03214318,  0.03198773,  0.03247273, ..., -0.23745778,
       -0.25386667, -0.27308622], dtype=float32), array([ 0.02520537,  0.02504992,  0.02553493, ..., -0.25324726,
       -0.2512351 , -0.25681826], dtype=float32), array([ 0.0309962 ,  0.03084075,  0.03132576, ..., -0.26371098,
       -0.26648352, -0.26739377], dtype=float32), array([-0.10183561, -0.10199106, -0.10150605, ..., -0.21140805,
       -0.21866488, -0.22424805], dtype=float32), array([-0.09361434, -0.09376979, -0.09328479, ..., -0.20649955,
       -0.21377659, -0.21935976], dtype=float32), array([-0.10393888, -0.10409433, -0.10803407, ..., -0.21259856,
       -0.22422066, -0.22980383], dtype=float32), array([-0.01205409, -0.01220953, -0.01172453, ...,  0.41167185,
        0.43549502,  0.43523103], dtype=float32), array([-0.01390421, -0.01405966, -0.01900947, ...,  0.41629717,
        0.46787244,  0.41337627], dtype=float32), array([-0.0010218 , -0.00117725, -0.00069225, ...,  0.38673654,
        0.38925505,  0.43129092], dtype=float32), array([ 0.01244622,  0.01229078,  0.01277578, ..., -0.23038708,
       -0.23315962, -0.2538943 ], dtype=float32), array([-0.05480158, -0.05495703, -0.05903822, ..., -0.23716721,
       -0.24450596, -0.24552292], dtype=float32), array([-0.04345256, -0.04360801, -0.04312301, ..., -0.23798734,
       -0.24075988, -0.25119743], dtype=float32), array([ 0.01305228,  0.01289684,  0.01338184, ..., -0.18609414,
       -0.19886668, -0.20444985], dtype=float32), array([ 0.00374538,  0.00358993,  0.00902539, ..., -0.20703474,
       -0.20980728, -0.20548946], dtype=float32), array([ 0.00270051,  0.00254506,  0.00303006, ..., -0.20561676,
       -0.21341443, -0.21397246], dtype=float32), array([ 0.0212388 ,  0.02108335,  0.01638705, ...,  0.28439805,
        0.26608151,  0.26049834], dtype=float32), array([ 0.00305229,  0.00289685,  0.00338185, ...,  0.28390583,
        0.26613331,  0.26055014], dtype=float32), array([ 0.01721472,  0.01705927,  0.01754427, ...,  0.29281446,
        0.27481353,  0.26923037], dtype=float32), array([ 0.01965421,  0.01949877,  0.02483815, ..., -0.19429803,
       -0.20192493, -0.2075081 ], dtype=float32), array([ 0.02116549,  0.02101004,  0.02621204, ..., -0.19260359,
       -0.20009309, -0.21039325], dtype=float32), array([ 0.02610391,  0.02594846,  0.02643347, ..., -0.18372324,
       -0.19119063, -0.20146863], dtype=float32), array([ 0.02522618,  0.02507073,  0.02555573, ...,  0.26412329,
        0.25700289,  0.24272406], dtype=float32), array([ 0.0182578 ,  0.01810235,  0.02315354, ...,  0.20118895,
        0.1847178 ,  0.17913464], dtype=float32), array([ 0.02840942,  0.02825397,  0.01981044, ...,  0.23015586,
        0.22738332,  0.22180015], dtype=float32)]
In [472]:
test_normed_array_of_faces = remove_average_pic(test_np_img, average_pic)
test_normed_array_of_faces = [x[0] for x in test_normed_array_of_faces]

print test_normed_array_of_faces
[array([ 0.00670159,  0.00654614,  0.00703114, ...,  0.09701014,
        0.08949825,  0.08391508], dtype=float32), array([ 0.02520537,  0.02504992,  0.02553493, ..., -0.2293238 ,
       -0.23688105, -0.24724889], dtype=float32), array([  5.88536263e-04,   4.33087349e-04,   9.18090343e-04, ...,
         6.45765781e-01,   6.71978712e-01,   6.85719252e-01], dtype=float32), array([ 0.01025033,  0.01009488,  0.01057988, ..., -0.26244679,
       -0.26038843, -0.2659716 ], dtype=float32), array([ 0.04549325,  0.0453378 ,  0.0458228 , ...,  0.00992948,
        0.00715694,  0.02125883], dtype=float32), array([-0.0069477 , -0.00710315, -0.00161815, ..., -0.17609414,
       -0.17386669, -0.18444985], dtype=float32), array([-0.0240351 , -0.01933616, -0.01885116, ...,  0.00958547,
       -0.07571134, -0.06673141], dtype=float32), array([ 0.02416342,  0.02400798,  0.02449298, ...,  0.0233503 ,
        0.03391111,  0.03277239], dtype=float32), array([-0.01694769, -0.01710314, -0.01661813, ..., -0.22609416,
       -0.2288667 , -0.23944986], dtype=float32), array([-0.00947195, -0.00477308, -0.00428808, ..., -0.11177376,
       -0.1145463 , -0.12498383], dtype=float32), array([-0.05922282, -0.0487963 , -0.03772926, ...,  0.20155135,
        0.26227087,  0.16674063], dtype=float32), array([-0.00675994, -0.00222051, -0.00173551, ..., -0.03348851,
       -0.02217653, -0.04653907], dtype=float32), array([-0.02168453, -0.02710313, -0.04767078, ...,  0.03574798,
       -0.01965615, -0.0410288 ], dtype=float32), array([ 0.02116549,  0.02101004,  0.02149504, ..., -0.25392434,
       -0.27084783, -0.28114796], dtype=float32), array([-0.10551912, -0.10567456, -0.10518956, ..., -0.21627271,
       -0.21904525, -0.22016414], dtype=float32), array([-0.03057462, -0.03073007, -0.03024507, ...,  0.22222194,
        0.20390534,  0.21386623], dtype=float32), array([ 0.0035336 ,  0.00337815,  0.00921071, ..., -0.23692302,
       -0.23969556, -0.25062633], dtype=float32), array([-0.00734973, -0.00750518, -0.00702018, ..., -0.19556651,
       -0.19833905, -0.20392221], dtype=float32), array([ 0.00162375,  0.0014683 ,  0.0019533 , ...,  0.2448242 ,
        0.23184758,  0.22116238], dtype=float32), array([ 0.03039825,  0.0302428 ,  0.0307278 , ..., -0.20630741,
       -0.19960128, -0.20518444], dtype=float32), array([ 0.02480668,  0.02465123,  0.02075028, ...,  0.21206376,
        0.21367717,  0.20370805], dtype=float32)]

2.Zeigen Sie das Durchschnittsbild mithilfe der matplotlib.pyplot_-Funktion _imshow() an. Hierzu muss das eindimensionale Numpy Array, welches das Durchschnittsbild enthält, in ein zweidimensionales Array der ursprünglichen Bildgröße umgewandelt werden (Numpy Funktionen shape() oder reshape())

Wichtiger Hinweis: Das Numpy-Array NormedArrayOfFaces ist die Transpornierte $X^T$ der Matrix $X$ aus Gleichung $X$.

In [473]:
import matplotlib.pyplot as plt
import matplotlib.image as mpimg

fig = plt.figure(figsize=(10,5))
a=fig.add_subplot(1,2,1)

imgplot = plt.imshow(average_pic, cmap='gray')
plt.colorbar()

a=fig.add_subplot(1,2,2)
imgplot = plt.imshow(average_pic)
plt.colorbar()
Out[473]:
<matplotlib.colorbar.Colorbar at 0x1374010d0>

Beobachtung

Die helleren/röteren Stellen haben in den meisten Bildern die größte Ähnlichkeit.

Beobachtungen

Die weißen/roten Stellen zeigen, welche Flächen am Durchschnittlichsten sind.
Die dunklen/blauen Flächen hingegen, wo es die meiste Abweichung gibt.

In [474]:
def print_faces(faces):
    fig = plt.figure(figsize=(10, len(faces) * 4))
    
    for index, face in enumerate(faces):
        print_face = np.copy(face)
        print_face.shape = (220, 150)
        
        a=fig.add_subplot(len(faces), 2, index * 2 + 1)
        imgplot = plt.imshow(print_face, cmap='gray')
        plt.colorbar()
        
        a=fig.add_subplot(len(faces), 2, index * 2 + 2)
        imgplot = plt.imshow(print_face)
        plt.colorbar()
In [475]:
normed_faces_print = [face for index, face in enumerate(normed_array_of_faces) if index % 3 == 0]

print_faces(normed_faces_print)

Beobachtung

Nach dem Subtrahieren des Average-Face können wir bei den Normed-Faces folgendes beobachten:

  • Je dunkler/blauer die Fläche, desto stärker weicht das jeweilge Bild an dieser Stelle vom Durchschnittsgesicht ab. (Stimmt stärker mit dem Durchschnitt überein)
  • Je heller/röter die Fläche, desto weniger weicht das jeweilige Bild an dieser Stelle vom Durchschnittsgesicht ab. (Stimmt weniger stark mit dem Durchschnitt überein)

Einige Bilder (die Person ist klein) zeigen schön, dass dieses Foto nicht zu den anderen passt, da der Kopf zu weit unten im Bild anfängt.

Berechnung der Eigenfaces

Aufgaben:

1.Implementieren Sie die Funktion calculateEigenfaces(adjfaces,width,height). Dieser Funktion werden die normierten Bilder NormedArrayOfFaces zusammen mit der Bildbreite und -höhe übergeben. Zurück liefert die Funktion ein Numpy-Array, dessen Zeilen die berechneten normierten Eigenfaces sind. Die Berechnung der Eigenfaces ist im Theorieteil Abschnitt Bestimmung der Eigenfaces beschrieben. Für die Python-Implementierung können Sie folgende Hinweise berücksichtigen:

* Berechnung der transponierten eines Numpy-Arrays $A$ mit der Numpy-Methode _transpose()_
* Matrixmultiplikation zweier Numpy-Arrays $A$ und $B$ mit der Numpy-Funktion _dot()_
* Berechnung der Eigenvektoren und Eigenvalues eines Numpy Arrays $A$ mit der Numpy-Funktion _linalg.eigh()_
* Sortierung von Numpy-Arrays mit den Numpy-Funktionen _sort()_ und _argsort()_.
In [476]:
import numpy

def calculate_eigenfaces(adjfaces,width,height):
    cov_matrix = np.dot(adjfaces, np.transpose(adjfaces))
    
    eigenvalues, eigenvectors = numpy.linalg.eigh(cov_matrix)
    eigenfaces = np.dot(np.transpose(adjfaces), eigenvectors)
    
    return eigenfaces
In [477]:
eigenfaces = calculate_eigenfaces(normed_array_of_faces, 150, 220)

eigenfaces
Out[477]:
array([[  1.11525878e-07,   5.95771475e-03,  -2.54181214e-04, ...,
          4.83602062e-02,   3.52607369e-02,  -1.59304678e-01],
       [ -5.22937626e-07,   6.25631213e-03,   2.68588634e-03, ...,
          4.60551642e-02,   2.87385080e-02,  -1.53797716e-01],
       [ -2.36788765e-07,   7.25927297e-03,  -3.98691278e-03, ...,
          5.32342196e-02,   3.09527088e-02,  -1.50757745e-01],
       ..., 
       [  1.84401870e-07,  -4.95539978e-04,   6.53242692e-03, ...,
         -2.61745155e-01,   5.14267683e-01,  -3.87679428e-01],
       [  1.24797225e-07,  -2.12744391e-03,  -2.11680681e-03, ...,
         -3.02636623e-01,   5.27391016e-01,  -3.90274674e-01],
       [  3.79979610e-07,  -7.17358338e-03,  -1.29947700e-02, ...,
         -3.37121993e-01,   5.08935392e-01,  -4.36609417e-01]], dtype=float32)

2.Aus dem von der Funktion calculateEigenfaces(adjfaces,width,height) zurück gelieferten Array von Eigenfaces sind die $K$ relevantesten auszuwählen. Dieses reduzierte Array wird im Folgenden mit Usub bezeichnet. Im Versuch kann $K=6$ eingestellt werden.

In [478]:
k = 8

k_best = eigenfaces[:,-(k):]
k_best
Out[478]:
array([[-0.01990438, -0.02062696, -0.08405811, ...,  0.04836021,
         0.03526074, -0.15930468],
       [-0.02024694, -0.02180661, -0.08509262, ...,  0.04605516,
         0.02873851, -0.15379772],
       [-0.01791114, -0.02514591, -0.07855982, ...,  0.05323422,
         0.03095271, -0.15075774],
       ..., 
       [-0.68919724,  0.26619378, -0.75439763, ..., -0.26174515,
         0.51426768, -0.38767943],
       [-0.62573749,  0.31385422, -0.74272549, ..., -0.30263662,
         0.52739102, -0.39027467],
       [-0.59686261,  0.39578062, -0.77559018, ..., -0.33712199,
         0.50893539, -0.43660942]], dtype=float32)

3.Zeigen Sie die $K=6$ wichtigsten Eigenfaces als Bilder mit der matplotlib.pyplot_-Funktion _imshow() an.

In [479]:
print_faces(np.transpose(k_best))

Beobachtung

Die einzelnen Eigenfaces beschreiben jeweils eine Dimension im Eigenface Raum.
Beim betrachten der Bilder fallen besonders die stark dunklen/grellen bereiche auf.
Z.B. kann man beim ersten Bild erkennen, dass eine Person mit relativ hoher Strirn in dieser Dimension einen ziemlich hohen Wert erreichen wird. Das zweite Bild hingegen zeigt einen starken Kontrast im Bereich der Brille. Daraus lässt sich wiederrum schließen, dass eine Person mit Brille in dieser Dimension einen hohen Wert haben wird.

Transformation der normierten Trainingsbilder in den Eigenface Raum

Aufgabe:

Die im vorigen Schritt angelegten $K$ relevantesten Eigenfaces spannen den sogenannten Eigenface-Raum auf. Für jedes der normierten Trainingsbilder, also für jede Zeile aus NormedArrayOfFaces, sind die Koordinaten im Eigenface-Raum entsprechend der Gleichung für $\omega_{k,i}$ definierten Transformation zu berechnen.

In [480]:
coordinates = []

for normed_face in normed_array_of_faces:
    face_coords = []
    
    for k in np.transpose(k_best):
        position = np.dot(np.transpose(k), normed_face)
        face_coords.append(position)
    
    coordinates.append(face_coords)
    
print coordinates
[[-73.661667, 388.30927, -50.936073, 399.68344, -1083.0452, 585.5564, -387.4563, 1358.9648], [-90.817924, -293.7402, -188.59927, 548.83258, -763.88745, 1305.853, -425.81955, 3115.4983], [139.78018, -353.64984, -742.14539, 367.80734, -1076.8247, 731.63287, -372.89752, 3057.5955], [53.475357, 312.27209, 1382.48, 818.55731, -1054.6465, 19.153458, 485.18158, -1099.106], [78.81424, 399.92651, 1344.4095, 796.78162, -882.69904, 389.19254, 548.93976, -984.48877], [72.847382, 332.15863, 1191.645, 686.75458, -114.57018, 329.46967, 374.68921, 783.54254], [-311.18008, 124.27039, -351.77997, 803.05573, -775.59448, 1073.1355, 1772.9294, 1897.4523], [-225.84467, -12.763046, -560.875, 874.22504, -766.7533, 1319.9534, 1916.2788, 1894.9382], [-450.00079, 181.32869, -383.57138, 790.20746, -581.75116, 1203.8254, 1707.8445, 1347.6584], [-647.54156, -429.45697, -27.90448, -735.30261, 1626.6753, -369.97794, -2005.1897, 4278.2354], [-357.92389, 245.78644, 877.24323, 87.116577, 521.14404, 1113.2126, -1323.4575, 4951.8042], [-347.27097, 0.43429565, 325.28204, -882.91699, 501.90253, 93.549637, -1662.5813, 4777.2705], [-514.61719, 210.37166, -200.77342, -1169.679, -1651.7356, 1257.1887, -2289.7397, -4742.9644], [235.3567, 863.19342, -865.23193, -457.78662, -134.963, 1913.0061, -1964.8643, -5044.2891], [-186.77861, 712.90833, -508.47131, -984.16449, -1091.1576, 1622.3563, -2295.4717, -4968.0625], [602.21826, 159.06335, -262.86429, -608.52722, 629.63745, -1315.6494, 1931.465, 152.73044], [424.77997, -110.82288, -43.643272, -525.58978, 49.660248, -1533.5698, 1878.8536, 259.87628], [44.252808, -54.07798, 283.29919, -99.838654, -405.79456, -1755.1606, 1791.7954, -1266.8323], [-116.38929, -109.68833, -463.48288, 436.62384, 802.2309, 673.75702, 1155.8218, -5300.7456], [340.64594, 405.7988, -450.95477, 399.26978, 1187.9792, 1020.8927, 1521.0035, -4749.4434], [410.59326, 481.88892, -592.09729, 595.83569, 1222.8063, 1386.2942, 1265.8972, -4972.9766], [170.27359, -538.93079, 194.31308, -1956.8528, 132.58266, 886.3562, 950.78558, 825.13], [144.74049, -565.04114, 40.879177, -1980.1184, 157.66087, 1089.3645, 971.36072, 1231.3617], [-75.665932, -312.30133, 398.57166, -1308.1848, -386.93182, 1282.5088, 684.88025, -2158.4763], [635.23877, -258.38171, -180.8421, 49.39917, -477.8457, -972.02948, 2086.2852, 1672.817], [607.95599, -360.12372, -573.10376, -64.02771, 413.62872, -435.16455, 1866.6989, 2752.7422], [349.48187, -43.699516, -907.3103, 9.1994476, 1272.5579, -108.90474, 1379.9476, 3278.1472], [-477.62863, -434.53632, 682.40363, -88.236267, 501.02219, -306.7959, -288.76373, -4154.6191], [292.37753, 891.54767, 564.23364, 19.579224, 2313.9045, 865.85022, 187.77472, -1909.965], [177.62711, 810.80573, 466.22153, -251.23607, 2258.9255, 1097.7012, 649.83813, -671.2085], [3.5771332, 429.27017, -417.22125, 229.40712, -555.1925, -962.16266, -905.5213, -580.36798], [-46.859955, 110.48694, -1225.6206, 72.815323, -343.19006, -1469.2002, -564.96552, 530.08923], [137.09038, 93.126053, -1724.9182, -27.164043, -19.493057, -1481.7266, -401.72372, 59.107605], [115.44482, -467.77408, -369.86801, 398.4537, -1218.3279, 1410.5261, -503.75397, 3629.7656], [165.25578, -334.8873, -343.05664, 440.32312, -1357.7526, 1324.178, -468.45364, 3218.8862], [135.88812, -425.85559, -385.46313, 420.44467, -1256.7959, 1449.1073, -383.84161, 3410.9482], [855.63306, 319.31781, -525.08514, -581.31403, -1689.9733, -1095.3887, -1509.8303, 1903.8015], [260.43842, 661.89624, 316.1958, -572.06213, -1519.9913, -1071.0464, -1277.228, 694.55811], [602.82471, 501.24445, -69.006866, -520.21729, -1888.2654, -1257.5083, -1331.6428, 1279.593], [926.33337, -710.6814, -19.67244, 925.64569, 1020.8262, 36.389771, -2543.5361, -4687.4795], [398.51135, -1269.033, 305.24274, 683.42859, 635.76587, -60.087189, -1783.0366, -4808.8296], [719.09875, -1104.9211, 240.54846, 935.5473, 732.54553, 102.29083, -2333.3989, -4817.063], [355.11365, -64.874649, 798.87347, 540.0957, -730.54346, 101.17455, 254.69312, 1419.8723], [298.00775, -16.404978, 780.03772, 566.07178, -718.03418, 89.72905, 236.71246, 1337.9429], [64.095558, 41.132172, 883.40308, 696.06439, -459.92035, 348.01144, -28.432899, 1358.3196], [179.24585, 516.54126, 551.83264, -504.22955, -96.671745, -1825.5085, 93.486649, -604.35754], [132.03529, 573.14331, 547.00952, -489.78088, 540.698, -1576.4841, -78.503952, -188.203], [48.203522, 294.22189, 547.9563, -242.41272, -620.89368, -2175.1831, -137.91737, -969.03162], [-276.67606, -493.854, -36.688751, 57.178375, 1572.334, -799.07642, -2251.28, 3446.627], [-256.79535, 378.11639, 522.33594, 126.99744, 965.89459, -160.21326, -1691.8033, 2620.8816], [-326.59503, -315.00128, 173.19202, 111.85292, 1434.1869, -266.02191, -2220.8572, 3478.9724], [-398.22525, 611.47607, -926.23767, 469.90479, 2489.4644, -315.71304, -488.94925, 2086.1707], [-482.61118, 506.87164, -288.82098, 557.68439, 1249.3574, 108.56494, 33.730072, 2820.29], [-93.272758, 337.16168, -51.76593, 875.71057, 445.46075, -305.17242, -8.5964813, 3610.3525], [-573.67108, -353.50397, -82.134766, 412.80502, 229.14377, -2133.604, -28.21225, -3273.0535], [-525.13062, -252.72812, -145.98863, 84.091629, 10.114952, -2614.3127, 467.41599, -1728.4015], [-602.81018, -299.23517, -105.67683, 145.41315, -208.31105, -2796.8457, 619.17572, -1502.2312], [34.458427, -498.60904, 559.58319, -802.65424, -310.80688, 607.2749, 1685.7838, 289.71759], [6.9817581, -422.03476, 410.86151, -957.17786, 711.77972, 1024.9618, 1537.5852, 145.66132], [-72.178879, -376.94708, 48.052261, -902.25061, 1409.5308, 1312.4912, 1184.5551, 735.66522], [-940.80701, -248.77551, -309.96539, 212.80161, -1074.0016, 60.270477, 558.71735, -4203.2705], [-892.49707, -214.88837, 113.85797, 60.024353, -790.52264, 40.365417, 998.1167, -3169.6716], [-855.24493, -146.84177, -168.19168, 6.0356293, -932.53876, -112.65976, 1159.4844, -3157.814]]

Erkennung

Aufgaben:

1.Wählen Sie ein Bild aus dem Verzeichnis test aus. Das ausgewählte zu erkennende Bild ist als Numpy-Array darzustellen. Eine Normierung der Pixelwerte in den Bereich zwischen 0 und 1 ist durchzuführen (wie bereits oben beschrieben). Schließlich muss auch von diesem Bild das Durchschnittsbild aller Trainingsbilder abgezogen werden. Diese Prozessschritte entsprechen der oben beschriebenen Vorverarbeitung der Trainingsbilder. Das resultierende normierte und mittelwertfreie Bild wird im Folgenden mit NormedTestFace bezeichnet.

In [481]:
normed_test_face = test_normed_array_of_faces[0]

plot_normed_face = np.copy(normed_test_face)
plot_normed_face.shape = (220,150)

fig = plt.figure(figsize=(10,5))
a=fig.add_subplot(1,2,1)

imgplot = plt.imshow(plot_normed_face, cmap='gray')
plt.colorbar()

a=fig.add_subplot(1,2,2)
imgplot = plt.imshow(plot_normed_face)
plt.colorbar()
Out[481]:
<matplotlib.colorbar.Colorbar at 0x1133ef550>

2.Danach sind die Koordinaten des NormedTestFace im Eigenface-Raum nach Gleichung $\omega_{k}$ zu berechnen und das in diesem Raum nächstliegende Trainingsbild zu bestimmen.

In [482]:
test_face_coords = []

for k in np.transpose(k_best):
    position = np.dot(np.transpose(k), normed_test_face)
    test_face_coords.append(position)

print test_face_coords
[95.244957, -172.33521, -258.18445, 25.035706, -959.31598, 299.73737, 248.17088, 1233.8712]
In [483]:
from scipy.spatial import distance

smallest_dist = None
smallest_index = None
nearest_img = []

print get_nearest_img(test_face_coords, coordinates)
(0, 1021.1935424804688, [-73.661667, 388.30927, -50.936073, 399.68344, -1083.0452, 585.5564, -387.4563, 1358.9648])

Aufgaben:
1.Führen Sie die implementierte Gesichtserkennung für alle Bilder im Verzeichnis test aus. Zeigen Sie jeweils das Testbild, das zugehörige erkannte Bild und die Distanz zwischen beiden Bildern an.

In [484]:
from scipy.spatial import distance

def get_nearest_img(face_coords, coordinates):
    smallest_dist = None
    smallest_index = None
    nearest_img = []

    for index, img in enumerate(coordinates):
        dst = distance.euclidean(face_coords, img)

        if index == 0:
            smallest_dist = dst
            nearest_img = img
            smallest_index = index

        if dst < smallest_dist:
            smallest_dist = dst
            nearest_img = img
            smallest_index = index

    return smallest_index, smallest_dist, nearest_img   

    # index is the index of the img from the training
    # dist ist the distance
    # nearest_img ist the coordinate of the img of train
In [485]:
test_coordinates = []

for test_normed_face in test_normed_array_of_faces:
    face_coords = []
    
    for k in np.transpose(k_best):
        position = np.dot(np.transpose(k), test_normed_face)
        face_coords.append(position)
    
    test_coordinates.append(face_coords)
    
print test_coordinates
[[95.244957, -172.33521, -258.18445, 25.035706, -959.31598, 299.73737, 248.17088, 1233.8712], [-179.30597, 477.34021, 970.92902, 741.06763, -743.42157, 310.59747, 7.8565979, -2021.5657], [-260.03394, 241.48666, -20.440964, 784.62073, -844.4115, 960.82825, 1554.8516, 1410.4415], [-468.6543, 169.23944, 861.30792, -488.64142, 1081.2539, 255.73447, -1088.5059, 3725.9124], [-582.14545, 11.921669, -338.52899, -1019.6866, -1407.9271, 696.91919, -2152.866, -4472.3403], [269.07022, 176.29121, 144.42436, -113.52679, 395.95969, -1196.187, 1607.4155, -1638.2731], [518.05505, 531.85059, -470.75916, 892.07349, 1173.9453, 1272.6528, 64.568909, -4262.647], [-316.54379, -394.45358, -329.159, -302.71652, -890.05579, 902.6225, -989.36029, -5045.4473], [563.64136, -152.75452, -83.544037, 89.587646, -560.67847, -946.60254, 2133.96, 1346.511], [-560.89215, -414.67532, 958.59851, -472.1167, -187.17749, -1136.2429, 153.08789, -1832.7131], [26.96051, 27.53775, -1432.7344, 69.702881, -467.66208, -1540.5081, -564.71503, 391.66736], [88.56411, 190.6051, 169.68732, 361.78348, -1369.0483, 1071.5295, -388.84146, 1761.5735], [866.71643, 160.77124, -642.45026, -561.07471, -1671.7236, -1094.8914, -1615.8647, 1000.0008], [166.37544, -1012.9734, 479.2804, 159.10689, 410.94012, -349.51776, -1169.3853, -4181.626], [348.72354, -4.3236504, 682.97864, 531.79919, -690.69312, -5.5612411, 224.851, 1606.2451], [261.19391, -11.146736, -74.581894, -251.81949, -1196.4353, -2103.4629, 59.509697, -102.632], [-164.33682, -375.53531, 183.258, 107.92885, 1113.5939, -448.74707, -2152.8198, 3795.3191], [507.71185, -65.372826, 134.29947, 665.67725, -158.49948, -623.21796, 331.98975, 2849.8831], [-671.88098, -422.33759, -23.646805, 375.35498, -191.81194, -2301.1113, -135.83696, -2686.2578], [-74.326973, -316.49994, 671.66022, -716.27924, -122.45718, 600.4397, 1621.1116, -1088.6028], [-885.15253, -376.32297, -373.96118, 234.41925, -1262.48, 13.076553, -154.2334, -4264.8057]]

Zeigen Sie jeweils das Testbild, das zugehörige erkannte Bild und die Distanz zwischen beiden Bildern an

In [486]:
matched_img = []

for img in test_coordinates:
    matched_img.append(get_nearest_img(img, coordinates))
    
In [487]:
print_img = []

for index in range(len(matched_img)):
    print_img.append(test_normed_array_of_faces[index])
    print_img.append(normed_array_of_faces[matched_img[index][0]])
    

distances = [img[1] for img in matched_img]
In [488]:
def print_compare_faces(faces, distances):
    fig = plt.figure(figsize=(10, len(faces) * 4))
    
    for index, face in enumerate(faces):
        print_face = np.copy(face)
        print_face.shape = (220, 150)
        
        a=fig.add_subplot(len(faces) / 2, 2, index + 1)

        if index % 2 == 0:
            a.set_title('Distance: ' + str(distances[index / 2]))
            
        imgplot = plt.imshow(print_face, cmap='gray')
In [489]:
print_compare_faces(print_img, distances)

2.Bestimmen Sie für die Werte $K=5,K=10$ und $K=15$ ($K$ ist die Anzahl der verwendeten Eigenfaces) die Rate falsch erkannter Bilder.

k = 1 -> 17/21 falsche Bilder
k = 3 -> 9/21 falsche Bilder
k = 5 -> 4/21 falsche Bilder
k = 6 -> 3/21 falsche Bilder
k = 7 -> 3/21 falsche Bilder
k = 8 -> 2/21 falsche Bilder
k = 10 -> 2/21 falsche Bilder
k = 15 -> 2/21 falsche Bilder
k = 20 -> 2/21 falsche Bilder
k = 25 -> 2/21 falsche Bilder
k = 50 -> 2/21 falsche Bilder

Beobachtung

Ab k= 8 bleibt der Fehlerwert konstant auf 2/21.
Selbst bei einem Wert von k=50 verbessert sich die Fehlerrate nicht mehr.